#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
文件加密解密模块

提供对文件的加密和解密功能，支持多种加密算法
"""

import os
import base64
import hashlib
import secrets
from pathlib import Path
from typing import Union, Optional, Dict, Any, BinaryIO, Tuple, List

# 导入异常处理装饰器
from ..exception.exception_handler import exception_handler
from ..exception.exceptions import FileException, CryptoException

# 导入日志模块
from ..logger.logger import Logger

# 创建日志记录器
logger = Logger("crypto_handler").get_logger()


class CryptoHandler:
    """
    文件加密解密处理类
    
    提供对文件的加密和解密功能，支持多种加密算法
    
    注意：此类需要安装cryptography库
    pip install cryptography
    """
    
    def __init__(self):
        """
        初始化加密解密处理器
        """
        try:
            from cryptography.fernet import Fernet
            from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
            from cryptography.hazmat.primitives import hashes
            from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
            
            self.Fernet = Fernet
            self.PBKDF2HMAC = PBKDF2HMAC
            self.hashes = hashes
            self.Cipher = Cipher
            self.algorithms = algorithms
            self.modes = modes
        except ImportError as e:
            logger.error(f"初始化加密解密处理器失败: {e}")
            logger.error("请安装必要的依赖: pip install cryptography")
            raise ImportError("请安装必要的依赖: pip install cryptography") from e
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def generate_key(self, password: Optional[str] = None, salt: Optional[bytes] = None) -> bytes:
        """
        生成加密密钥
        
        Args:
            password: 用于生成密钥的密码，默认为None表示生成随机密钥
            salt: 盐值，默认为None表示生成随机盐值
            
        Returns:
            生成的密钥
            
        Raises:
            CryptoException: 生成密钥失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 生成随机密钥
            >>> key = handler.generate_key()
            >>> # 使用密码生成密钥
            >>> key = handler.generate_key(password="my-secure-password")
        """
        try:
            if password is None:
                # 生成随机密钥
                key = self.Fernet.generate_key()
            else:
                # 使用密码生成密钥
                if salt is None:
                    salt = os.urandom(16)
                
                # 使用PBKDF2HMAC派生密钥
                kdf = self.PBKDF2HMAC(
                    algorithm=self.hashes.SHA256(),
                    length=32,
                    salt=salt,
                    iterations=100000,
                )
                key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
            
            logger.info("生成加密密钥成功")
            return key
        except Exception as e:
            raise CryptoException(f"生成加密密钥失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def encrypt_file(self, input_file: Union[str, Path], output_file: Union[str, Path], 
                   key: bytes, chunk_size: int = 64 * 1024) -> None:
        """
        加密文件
        
        Args:
            input_file: 输入文件路径
            output_file: 输出文件路径
            key: 加密密钥
            chunk_size: 分块大小（字节），默认为64KB
            
        Raises:
            CryptoException: 加密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> key = handler.generate_key()
            >>> # 加密文件
            >>> handler.encrypt_file("document.txt", "document.enc", key)
        """
        input_file = Path(input_file)
        output_file = Path(output_file)
        
        # 检查输入文件是否存在
        if not input_file.exists():
            raise CryptoException(f"输入文件不存在: {input_file}")
        
        # 确保输出目录存在
        output_file.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 创建Fernet加密器
            fernet = self.Fernet(key)
            
            # 分块加密文件
            with open(input_file, 'rb') as in_file, open(output_file, 'wb') as out_file:
                while True:
                    chunk = in_file.read(chunk_size)
                    if not chunk:
                        break
                    
                    # 加密数据块
                    encrypted_chunk = fernet.encrypt(chunk)
                    
                    # 写入加密数据块的长度和数据
                    out_file.write(len(encrypted_chunk).to_bytes(4, byteorder='big'))
                    out_file.write(encrypted_chunk)
            
            logger.info(f"加密文件成功: {input_file} -> {output_file}")
        except Exception as e:
            raise CryptoException(f"加密文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def decrypt_file(self, input_file: Union[str, Path], output_file: Union[str, Path], 
                   key: bytes) -> None:
        """
        解密文件
        
        Args:
            input_file: 输入文件路径（加密文件）
            output_file: 输出文件路径（解密文件）
            key: 解密密钥
            
        Raises:
            CryptoException: 解密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 解密文件（使用之前的加密密钥）
            >>> handler.decrypt_file("document.enc", "document_decrypted.txt", key)
        """
        input_file = Path(input_file)
        output_file = Path(output_file)
        
        # 检查输入文件是否存在
        if not input_file.exists():
            raise CryptoException(f"输入文件不存在: {input_file}")
        
        # 确保输出目录存在
        output_file.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 创建Fernet解密器
            fernet = self.Fernet(key)
            
            # 分块解密文件
            with open(input_file, 'rb') as in_file, open(output_file, 'wb') as out_file:
                while True:
                    # 读取加密数据块的长度
                    length_bytes = in_file.read(4)
                    if not length_bytes:
                        break
                    
                    # 解析长度
                    length = int.from_bytes(length_bytes, byteorder='big')
                    
                    # 读取加密数据块
                    encrypted_chunk = in_file.read(length)
                    if not encrypted_chunk:
                        break
                    
                    # 解密数据块
                    decrypted_chunk = fernet.decrypt(encrypted_chunk)
                    
                    # 写入解密数据
                    out_file.write(decrypted_chunk)
            
            logger.info(f"解密文件成功: {input_file} -> {output_file}")
        except Exception as e:
            raise CryptoException(f"解密文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def encrypt_text(self, text: str, key: bytes) -> str:
        """
        加密文本
        
        Args:
            text: 要加密的文本
            key: 加密密钥
            
        Returns:
            加密后的文本（Base64编码）
            
        Raises:
            CryptoException: 加密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> key = handler.generate_key()
            >>> # 加密文本
            >>> encrypted_text = handler.encrypt_text("Hello, World!", key)
        """
        try:
            # 创建Fernet加密器
            fernet = self.Fernet(key)
            
            # 加密文本
            encrypted_bytes = fernet.encrypt(text.encode('utf-8'))
            encrypted_text = encrypted_bytes.decode('utf-8')
            
            logger.info("加密文本成功")
            return encrypted_text
        except Exception as e:
            raise CryptoException(f"加密文本失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def decrypt_text(self, encrypted_text: str, key: bytes) -> str:
        """
        解密文本
        
        Args:
            encrypted_text: 加密的文本（Base64编码）
            key: 解密密钥
            
        Returns:
            解密后的文本
            
        Raises:
            CryptoException: 解密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 解密文本（使用之前的加密密钥）
            >>> decrypted_text = handler.decrypt_text(encrypted_text, key)
        """
        try:
            # 创建Fernet解密器
            fernet = self.Fernet(key)
            
            # 解密文本
            decrypted_bytes = fernet.decrypt(encrypted_text.encode('utf-8'))
            decrypted_text = decrypted_bytes.decode('utf-8')
            
            logger.info("解密文本成功")
            return decrypted_text
        except Exception as e:
            raise CryptoException(f"解密文本失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def encrypt_file_aes(self, input_file: Union[str, Path], output_file: Union[str, Path], 
                       key: bytes, iv: Optional[bytes] = None,
                       chunk_size: int = 64 * 1024) -> bytes:
        """
        使用AES加密文件
        
        Args:
            input_file: 输入文件路径
            output_file: 输出文件路径
            key: AES加密密钥（必须是16、24或32字节）
            iv: 初始化向量，默认为None表示生成随机IV
            chunk_size: 分块大小（字节），默认为64KB
            
        Returns:
            使用的初始化向量(IV)
            
        Raises:
            CryptoException: 加密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 生成AES密钥
            >>> key = os.urandom(32)  # 256位密钥
            >>> # 加密文件
            >>> iv = handler.encrypt_file_aes("document.txt", "document.enc", key)
            >>> # 保存密钥和IV以便后续解密
        """
        input_file = Path(input_file)
        output_file = Path(output_file)
        
        # 检查输入文件是否存在
        if not input_file.exists():
            raise CryptoException(f"输入文件不存在: {input_file}")
        
        # 确保输出目录存在
        output_file.parent.mkdir(parents=True, exist_ok=True)
        
        # 检查密钥长度
        if len(key) not in (16, 24, 32):  # AES-128, AES-192, AES-256
            raise CryptoException(f"无效的AES密钥长度: {len(key)}字节，必须是16、24或32字节")
        
        try:
            # 生成或使用提供的IV
            if iv is None:
                iv = os.urandom(16)  # AES块大小为16字节
            elif len(iv) != 16:
                raise CryptoException(f"无效的IV长度: {len(iv)}字节，必须是16字节")
            
            # 创建AES加密器
            encryptor = self.Cipher(
                self.algorithms.AES(key),
                self.modes.CBC(iv)
            ).encryptor()
            
            # 分块加密文件
            with open(input_file, 'rb') as in_file, open(output_file, 'wb') as out_file:
                # 写入IV
                out_file.write(iv)
                
                while True:
                    chunk = in_file.read(chunk_size)
                    if not chunk:
                        break
                    
                    # 确保数据块长度是16的倍数（AES块大小）
                    if len(chunk) % 16 != 0:
                        # PKCS7填充
                        padding_length = 16 - (len(chunk) % 16)
                        chunk += bytes([padding_length]) * padding_length
                    
                    # 加密数据块
                    encrypted_chunk = encryptor.update(chunk)
                    out_file.write(encrypted_chunk)
                
                # 写入最后的块
                final_chunk = encryptor.finalize()
                if final_chunk:
                    out_file.write(final_chunk)
            
            logger.info(f"使用AES加密文件成功: {input_file} -> {output_file}")
            return iv
        except Exception as e:
            raise CryptoException(f"使用AES加密文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def decrypt_file_aes(self, input_file: Union[str, Path], output_file: Union[str, Path], 
                       key: bytes, iv: Optional[bytes] = None,
                       chunk_size: int = 64 * 1024) -> None:
        """
        使用AES解密文件
        
        Args:
            input_file: 输入文件路径（加密文件）
            output_file: 输出文件路径（解密文件）
            key: AES解密密钥（必须是16、24或32字节）
            iv: 初始化向量，默认为None表示从文件中读取IV
            chunk_size: 分块大小（字节），默认为64KB
            
        Raises:
            CryptoException: 解密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 解密文件（使用之前的加密密钥和IV）
            >>> handler.decrypt_file_aes("document.enc", "document_decrypted.txt", key, iv)
        """
        input_file = Path(input_file)
        output_file = Path(output_file)
        
        # 检查输入文件是否存在
        if not input_file.exists():
            raise CryptoException(f"输入文件不存在: {input_file}")
        
        # 确保输出目录存在
        output_file.parent.mkdir(parents=True, exist_ok=True)
        
        # 检查密钥长度
        if len(key) not in (16, 24, 32):  # AES-128, AES-192, AES-256
            raise CryptoException(f"无效的AES密钥长度: {len(key)}字节，必须是16、24或32字节")
        
        try:
            with open(input_file, 'rb') as in_file, open(output_file, 'wb') as out_file:
                # 如果没有提供IV，从文件中读取
                if iv is None:
                    iv = in_file.read(16)
                elif len(iv) != 16:
                    raise CryptoException(f"无效的IV长度: {len(iv)}字节，必须是16字节")
                
                # 创建AES解密器
                decryptor = self.Cipher(
                    self.algorithms.AES(key),
                    self.modes.CBC(iv)
                ).decryptor()
                
                # 读取所有加密数据
                encrypted_data = in_file.read()
                
                # 解密数据
                decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
                
                # 移除PKCS7填充
                padding_length = decrypted_data[-1]
                if padding_length > 0 and padding_length <= 16:
                    # 检查填充是否有效
                    if all(b == padding_length for b in decrypted_data[-padding_length:]):
                        decrypted_data = decrypted_data[:-padding_length]
                
                # 写入解密数据
                out_file.write(decrypted_data)
            
            logger.info(f"使用AES解密文件成功: {input_file} -> {output_file}")
        except Exception as e:
            raise CryptoException(f"使用AES解密文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def calculate_file_hash(self, file_path: Union[str, Path], algorithm: str = 'sha256', 
                          chunk_size: int = 64 * 1024) -> str:
        """
        计算文件哈希值
        
        Args:
            file_path: 文件路径
            algorithm: 哈希算法，可选值：'md5', 'sha1', 'sha256', 'sha512'，默认为'sha256'
            chunk_size: 分块大小（字节），默认为64KB
            
        Returns:
            文件的哈希值（十六进制字符串）
            
        Raises:
            CryptoException: 计算哈希失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 计算文件SHA-256哈希值
            >>> hash_value = handler.calculate_file_hash("document.txt")
            >>> # 计算文件MD5哈希值
            >>> md5_hash = handler.calculate_file_hash("document.txt", algorithm='md5')
        """
        file_path = Path(file_path)
        
        # 检查文件是否存在
        if not file_path.exists():
            raise CryptoException(f"文件不存在: {file_path}")
        
        # 检查算法是否支持
        if algorithm.lower() not in ('md5', 'sha1', 'sha256', 'sha512'):
            raise CryptoException(f"不支持的哈希算法: {algorithm}")
        
        try:
            # 创建哈希对象
            if algorithm.lower() == 'md5':
                hash_obj = hashlib.md5()
            elif algorithm.lower() == 'sha1':
                hash_obj = hashlib.sha1()
            elif algorithm.lower() == 'sha256':
                hash_obj = hashlib.sha256()
            elif algorithm.lower() == 'sha512':
                hash_obj = hashlib.sha512()
            
            # 分块计算哈希值
            with open(file_path, 'rb') as f:
                while True:
                    chunk = f.read(chunk_size)
                    if not chunk:
                        break
                    hash_obj.update(chunk)
            
            # 获取哈希值
            hash_value = hash_obj.hexdigest()
            
            logger.info(f"计算文件哈希值成功: {file_path} ({algorithm})")
            return hash_value
        except Exception as e:
            raise CryptoException(f"计算文件哈希值失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def verify_file_hash(self, file_path: Union[str, Path], expected_hash: str, 
                       algorithm: str = 'sha256') -> bool:
        """
        验证文件哈希值
        
        Args:
            file_path: 文件路径
            expected_hash: 期望的哈希值（十六进制字符串）
            algorithm: 哈希算法，可选值：'md5', 'sha1', 'sha256', 'sha512'，默认为'sha256'
            
        Returns:
            哈希值是否匹配
            
        Raises:
            CryptoException: 验证哈希失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 验证文件哈希值
            >>> is_valid = handler.verify_file_hash("document.txt", "a1b2c3d4e5f6...")
        """
        try:
            # 计算文件哈希值
            actual_hash = self.calculate_file_hash(file_path, algorithm)
            
            # 比较哈希值（不区分大小写）
            is_valid = actual_hash.lower() == expected_hash.lower()
            
            if is_valid:
                logger.info(f"文件哈希值验证成功: {file_path}")
            else:
                logger.warning(f"文件哈希值验证失败: {file_path} (期望: {expected_hash}, 实际: {actual_hash})")
            
            return is_valid
        except Exception as e:
            raise CryptoException(f"验证文件哈希值失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def encrypt_directory(self, input_dir: Union[str, Path], output_dir: Union[str, Path], 
                       key: bytes, recursive: bool = True) -> Dict[str, Any]:
        """
        加密目录中的所有文件
        
        Args:
            input_dir: 输入目录路径
            output_dir: 输出目录路径
            key: 加密密钥
            recursive: 是否递归处理子目录，默认为True
            
        Returns:
            加密结果信息，包含成功和失败的文件列表
            
        Raises:
            CryptoException: 加密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> key = handler.generate_key()
            >>> # 加密整个目录
            >>> result = handler.encrypt_directory("documents", "encrypted_documents", key)
            >>> print(f"成功加密 {len(result['success'])} 个文件，失败 {len(result['failed'])} 个文件")
        """
        input_dir = Path(input_dir)
        output_dir = Path(output_dir)
        
        # 检查输入目录是否存在
        if not input_dir.exists() or not input_dir.is_dir():
            raise CryptoException(f"输入目录不存在: {input_dir}")
        
        # 确保输出目录存在
        output_dir.mkdir(parents=True, exist_ok=True)
        
        # 初始化结果
        result = {
            'success': [],
            'failed': []
        }
        
        try:
            # 遍历目录
            for root, dirs, files in os.walk(input_dir):
                # 如果不递归，跳过子目录
                if not recursive and root != str(input_dir):
                    continue
                
                # 计算相对路径
                rel_path = os.path.relpath(root, str(input_dir))
                
                # 创建对应的输出目录
                if rel_path == '.':
                    current_output_dir = output_dir
                else:
                    current_output_dir = output_dir / rel_path
                    current_output_dir.mkdir(parents=True, exist_ok=True)
                
                # 处理文件
                for file in files:
                    input_file = Path(root) / file
                    output_file = current_output_dir / f"{file}.enc"
                    
                    try:
                        # 加密文件
                        self.encrypt_file(input_file, output_file, key)
                        result['success'].append(str(input_file))
                    except Exception as e:
                        logger.error(f"加密文件失败: {input_file} - {e}")
                        result['failed'].append({
                            'file': str(input_file),
                            'error': str(e)
                        })
            
            logger.info(f"加密目录完成: {input_dir} -> {output_dir} (成功: {len(result['success'])}, 失败: {len(result['failed'])})")
            return result
        except Exception as e:
            raise CryptoException(f"加密目录失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def decrypt_directory(self, input_dir: Union[str, Path], output_dir: Union[str, Path], 
                       key: bytes, recursive: bool = True,
                       file_pattern: str = '*.enc') -> Dict[str, Any]:
        """
        解密目录中的所有加密文件
        
        Args:
            input_dir: 输入目录路径（加密文件目录）
            output_dir: 输出目录路径（解密文件目录）
            key: 解密密钥
            recursive: 是否递归处理子目录，默认为True
            file_pattern: 要解密的文件模式，默认为'*.enc'
            
        Returns:
            解密结果信息，包含成功和失败的文件列表
            
        Raises:
            CryptoException: 解密失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 解密整个目录（使用之前的加密密钥）
            >>> result = handler.decrypt_directory("encrypted_documents", "decrypted_documents", key)
            >>> print(f"成功解密 {len(result['success'])} 个文件，失败 {len(result['failed'])} 个文件")
        """
        input_dir = Path(input_dir)
        output_dir = Path(output_dir)
        
        # 检查输入目录是否存在
        if not input_dir.exists() or not input_dir.is_dir():
            raise CryptoException(f"输入目录不存在: {input_dir}")
        
        # 确保输出目录存在
        output_dir.mkdir(parents=True, exist_ok=True)
        
        # 初始化结果
        result = {
            'success': [],
            'failed': []
        }
        
        try:
            # 导入glob模块
            import fnmatch
            
            # 遍历目录
            for root, dirs, files in os.walk(input_dir):
                # 如果不递归，跳过子目录
                if not recursive and root != str(input_dir):
                    continue
                
                # 计算相对路径
                rel_path = os.path.relpath(root, str(input_dir))
                
                # 创建对应的输出目录
                if rel_path == '.':
                    current_output_dir = output_dir
                else:
                    current_output_dir = output_dir / rel_path
                    current_output_dir.mkdir(parents=True, exist_ok=True)
                
                # 处理文件
                for file in files:
                    # 检查文件模式
                    if not fnmatch.fnmatch(file, file_pattern):
                        continue
                    
                    input_file = Path(root) / file
                    
                    # 移除.enc扩展名
                    if file.endswith('.enc'):
                        output_file = current_output_dir / file[:-4]
                    else:
                        output_file = current_output_dir / file
                    
                    try:
                        # 解密文件
                        self.decrypt_file(input_file, output_file, key)
                        result['success'].append(str(input_file))
                    except Exception as e:
                        logger.error(f"解密文件失败: {input_file} - {e}")
                        result['failed'].append({
                            'file': str(input_file),
                            'error': str(e)
                        })
            
            logger.info(f"解密目录完成: {input_dir} -> {output_dir} (成功: {len(result['success'])}, 失败: {len(result['failed'])})")
            return result
        except Exception as e:
            raise CryptoException(f"解密目录失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def save_key_to_file(self, key: bytes, key_file: Union[str, Path], 
                       password: Optional[str] = None) -> None:
        """
        将密钥保存到文件
        
        Args:
            key: 要保存的密钥
            key_file: 密钥文件路径
            password: 用于加密密钥的密码，默认为None表示不加密
            
        Raises:
            CryptoException: 保存密钥失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> key = handler.generate_key()
            >>> # 保存密钥到文件
            >>> handler.save_key_to_file(key, "encryption.key")
            >>> # 使用密码保护密钥
            >>> handler.save_key_to_file(key, "encryption.key", password="my-secure-password")
        """
        key_file = Path(key_file)
        
        # 确保输出目录存在
        key_file.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            if password is None:
                # 直接保存密钥
                with open(key_file, 'wb') as f:
                    f.write(key)
            else:
                # 使用密码加密密钥
                # 生成盐值
                salt = os.urandom(16)
                
                # 使用PBKDF2HMAC派生密钥
                kdf = self.PBKDF2HMAC(
                    algorithm=self.hashes.SHA256(),
                    length=32,
                    salt=salt,
                    iterations=100000,
                )
                derived_key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
                
                # 使用派生密钥加密原始密钥
                fernet = self.Fernet(derived_key)
                encrypted_key = fernet.encrypt(key)
                
                # 保存盐值和加密的密钥
                with open(key_file, 'wb') as f:
                    f.write(salt)
                    f.write(encrypted_key)
            
            logger.info(f"保存密钥到文件成功: {key_file}")
        except Exception as e:
            raise CryptoException(f"保存密钥到文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=CryptoException)
    def load_key_from_file(self, key_file: Union[str, Path], 
                         password: Optional[str] = None) -> bytes:
        """
        从文件加载密钥
        
        Args:
            key_file: 密钥文件路径
            password: 用于解密密钥的密码，默认为None表示密钥未加密
            
        Returns:
            加载的密钥
            
        Raises:
            CryptoException: 加载密钥失败时抛出
            
        Examples:
            >>> handler = CryptoHandler()
            >>> # 加载未加密的密钥
            >>> key = handler.load_key_from_file("encryption.key")
            >>> # 加载加密的密钥
            >>> key = handler.load_key_from_file("encryption.key", password="my-secure-password")
        """
        key_file = Path(key_file)
        
        # 检查密钥文件是否存在
        if not key_file.exists():
            raise CryptoException(f"密钥文件不存在: {key_file}")
        
        try:
            with open(key_file, 'rb') as f:
                file_content = f.read()
            
            if password is None:
                # 直接加载密钥
                key = file_content
            else:
                # 解密密钥
                if len(file_content) <= 16:
                    raise CryptoException("密钥文件格式无效")
                
                # 提取盐值和加密的密钥
                salt = file_content[:16]
                encrypted_key = file_content[16:]
                
                # 使用PBKDF2HMAC派生密钥
                kdf = self.PBKDF2HMAC(
                    algorithm=self.hashes.SHA256(),
                    length=32,
                    salt=salt,
                    iterations=100000,
                )
                derived_key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
                
                # 使用派生密钥解密原始密钥
                fernet = self.Fernet(derived_key)
                key = fernet.decrypt(encrypted_key)
            
            logger.info(f"从文件加载密钥成功: {key_file}")
            return key
        except Exception as e:
            raise CryptoException(f"从文件加载密钥失败: {e}")